package com.real.fact.web.sale.application;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.Factory;
import org.apache.commons.collections.list.LazyList;
import org.apache.commons.lang.Validate;
import org.springframework.validation.Errors;

import com.globant.katari.core.application.ValidatableCommand;
import com.real.fact.web.customer.domain.Payment;
import com.real.fact.web.customer.domain.Customer;
import com.real.fact.web.customer.domain.CustomerRepository;
import com.real.fact.web.product.domain.Product;
import com.real.fact.web.product.domain.ProductRepository;
import com.real.fact.web.sale.domain.Check;
import com.real.fact.web.sale.domain.ItemSale;
import com.real.fact.web.sale.domain.Sale;
import com.real.fact.web.sale.domain.SalePayment;
import com.real.fact.web.sale.domain.SaleRepository;
import com.real.fact.web.sale.domain.Ticket;

/**
 *
 * @since Jul 3, 2011
 */
public class SaveSaleCommand implements ValidatableCommand<Void> {

  private final SaleRepository saleRepository;
  
  private final ProductRepository productRepository;
  
  private final CustomerRepository customerRepository;
  
  private List<ItemSaleDTO> items;

  private final Map<String, String> ticketTypes;
  
  private String ticketTypeSelected = Ticket.NONE.toString();
  
  private long customerId = 0;
  
  private String customerName;
  
  private String cuil;
  
  private float amountCheck;
  
  private float amountCreditCard;
  
  private float amountDebitCard;
  
  private float amountCash;
  
  private float amountAccount;
  
  private Date dateCheck;
  
  private String bankCheck;
  
  private String numCheck;

  @SuppressWarnings("unchecked")
  public SaveSaleCommand(SaleRepository theSaleRepository,
      ProductRepository theProductRepository,
      CustomerRepository theCustomerRepository) {
    Validate.notNull(theSaleRepository);
    Validate.notNull(theProductRepository);
    Validate.notNull(theCustomerRepository);
    saleRepository = theSaleRepository;
    productRepository = theProductRepository;
    customerRepository = theCustomerRepository;
    Factory factory = new Factory() {
      
      public Object create() {
        return new ItemSaleDTO();
      }
    };
    items = LazyList.decorate(new ArrayList<ItemSaleDTO>(), factory);
    ticketTypes = new HashMap<String, String>();
    ticketTypes.put(Ticket.A.toString(), "Factura A");
    ticketTypes.put(Ticket.B.toString(), "Factura B");
    ticketTypes.put(Ticket.NONE.toString(), "N/A");
    ticketTypes.put(Ticket.R.toString(), "Remito");
  }

  public Void execute() {
    List<ItemSale> itemsSale = new ArrayList<ItemSale>(items.size());
    for (int i = 0; i < items.size(); i++) {
      ItemSaleDTO item = items.get(i);
      itemsSale.add(new ItemSale(
          productRepository.findById(item.getProductId()), item.getAmount(),
          item.getPrice()));
    }
    Customer customer = null;
    if (customerId > 0) {
      customer = customerRepository.findById(customerId);
    }
    Check check = null;
    if (checkSelected()) {
      check = new Check(numCheck, bankCheck, amountCheck, dateCheck);
    }
    Sale sale = new Sale(new Date(), itemsSale, customer);
    saleRepository.save(sale);
    float totalPayed = getTotalPayed();
    Payment thePayment = new Payment(amountCash, amountDebitCard, amountCreditCard,
        customer = customerRepository.findById(customerId), "Payment for sale "
            + sale.getId(), check, Ticket.valueOf(ticketTypeSelected));
    customerRepository.save(thePayment);
    SalePayment salePayment = new SalePayment(thePayment, sale, Math.min(
        totalPayed, sale.getTotal()));
    saleRepository.save(salePayment);
    printTicketIfNeeded(sale);
    updateProductsStock(sale.getItems());
    attachToCustomerAccount(sale);
    return null;
  }

  private float getTotalPayed() {
    return amountCheck + amountCash + amountCreditCard
        + amountDebitCard;
  }

  /**
   * Calculates if the customer has not payed all the sale and
   * attaches the remaining into his/her account. 
   * @param sale the current sale.
   */
  private void attachToCustomerAccount(final Sale sale) {
    if (getTotalPayed() < sale.getTotal()) {
      Customer customer = sale.getCustomer();
      customer.addAdjustment(getTotalPayed() - sale.getTotal());
      customerRepository.save(customer);
    }
  }

  /**
   * Updates the involved products stock after a sale.
   * @param list a list of items.
   */
  private void updateProductsStock(List<ItemSale> list) {
    for (ItemSale item : list) {
      Product product = item.getProduct();
      product.decreaseAmount(item.getAmount());
      productRepository.save(product);
    }
  }

  private boolean checkSelected() {
    return amountCheck > 0;
  }

  private void printTicketIfNeeded(final Sale sale) {
    //TODO print ticket A B
  }

  public List<ItemSaleDTO> getItems() {
    return items;
  }

  public void validate(Errors errors) {
	 // TODO Auto-generated method stub
  }

  public String getTicketTypeSelected() {
    return ticketTypeSelected;
  }

  public void setTicketTypeSelected(String ticketTypeSelected) {
    this.ticketTypeSelected = ticketTypeSelected;
  }

  public Map<String, String> getTicketTypes() {
    return ticketTypes;
  }

  public long getCustomerId() {
    return customerId;
  }

  public void setCustomerId(long customerId) {
    this.customerId = customerId;
  }

  public String getCustomerName() {
    return customerName;
  }

  public void setCustomerName(String customerName) {
    this.customerName = customerName;
  }

  public String getCuil() {
    return cuil;
  }

  public void setCuil(String cuil) {
    this.cuil = cuil;
  }

  public float getAmountCheck() {
    return amountCheck;
  }

  public void setAmountCheck(float amountCheck) {
    this.amountCheck = amountCheck;
  }

  public Date getDateCheck() {
    return dateCheck;
  }

  public void setDateCheck(Date dateCheck) {
    this.dateCheck = dateCheck;
  }

  public String getBankCheck() {
    return bankCheck;
  }

  public void setBankCheck(String bankCheck) {
    this.bankCheck = bankCheck;
  }

  public String getNumCheck() {
    return numCheck;
  }

  public void setNumCheck(String numCheck) {
    this.numCheck = numCheck;
  }

  public float getAmountCreditCard() {
    return amountCreditCard;
  }

  public void setAmountCreditCard(float amountCreditCard) {
    this.amountCreditCard = amountCreditCard;
  }

  public float getAmountDebitCard() {
    return amountDebitCard;
  }

  public void setAmountDebitCard(float amountDebitCard) {
    this.amountDebitCard = amountDebitCard;
  }

  public float getAmountCash() {
    return amountCash;
  }

  public void setAmountCash(float amountCash) {
    this.amountCash = amountCash;
  }

  public float getAmountAccount() {
    return amountAccount;
  }

  public void setAmountAccount(float amountAccount) {
    this.amountAccount = amountAccount;
  }
  
}
